
#include "trainer.h"

/*
 *	some configuration definitions
 */
#define PROCESS_NAME _T("winmine.exe")
#define WINDOW_NAME _T("Minesweeper")

#define TRUE 1
#define FALSE 0
#define BOOL int
#define MOUSE_POST_MESSAGE_SUCCESS 1
#define PROG_RET_ERROR -1
#define PROG_RET_SUCCESS 0

#define BOARD_MEMORY_ADDRESS 0x01005340
#define BOARD_MAX_WIDTH 32
#define BOARD_MAX_HEIGHT 26

#define BORDER_BIT			0x10
#define MINE_BIT			0x80
#define REVEALED_BIT		0x40

#define CLICKED_QUESTION_MARK_VAL	0x09
#define REVEALED_MINE_VAL			0x0A
#define CROSSED_MINE_VAL			0x0B
#define EXPLODED_MINE_VAL			0x0C
#define QUESTION_MARK_VAL			0x0D
#define FLAG_VAL					0x0E
#define UNREVEALED_CELL_VAL			0x0F

#define UNREVEALED_EMPTY_CELL_CH	'.'
#define UNREVEALED_MINE_CELL_CH		'*'
#define REVEALED_CELL_WITH_NUM_CH	'0'
#define REVEALED_MINE_CH			'M'
#define EXPLODED_MINE_CH			'B'
#define QUESTION_MARK_CH			'?'
#define TRUE_QUESTION_MARK_CH		'!'
#define CROSSED_MINE_CH				'X'
#define FLAGED_CELL_CH				'F'
#define REVEALED_EMPTY_CELL_CH		'-'

#define VIEWER_MODE "-v"
#define SOLVER_MODE "-s"

#define SLEEP_PERIOD 50

//will hold the board content
char gGameBoard[BOARD_MAX_HEIGHT][BOARD_MAX_WIDTH];
//board dimensions
size_t gBoardWidth;
size_t gBoardHeight;



/*
 * find process ID by the provess' name
 * return TRUE on success and false on failure
 */
BOOL GetProcessIDByName(PCWSTR name,DWORD* pId)
{
    // Create toolhelp snapshot.
    HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    PROCESSENTRY32 process;
	BOOL retVal = FALSE;

    ZeroMemory(&process, sizeof(process));
    process.dwSize = sizeof(process);

	

	if (snapshot == INVALID_HANDLE_VALUE)
	{
		_tprintf(_T("Error: can't enumerate process list\n"));		
		return FALSE;
	}

	// Walkthrough all processes.
    if (Process32First(snapshot, &process))
    {
        do
        {
			//check if the process' name match the given name
			if (_tcsncmp(name,process.szExeFile,MAX_PATH) == 0)
			{
				*pId = process.th32ProcessID;
				retVal = TRUE;
				break;
			}
        } while (Process32Next(snapshot, &process));
		
		
    }

    CloseHandle(snapshot);

    
	return retVal;
	
}

/*
 * this function calculate the actual board size that surrounded by borders.
 * 
 */
void CalculateBoardActualSize()
{
	size_t counter = 0;

	//count the horizontal border's size
	while (counter < BOARD_MAX_WIDTH && (gGameBoard[0][counter] & BORDER_BIT))
	{
		++counter;
	}
	
	//ommit the left and right bosrders;
	gBoardWidth = counter - 2;

	counter = 0;

	//count the vertical border's size
	while (counter < BOARD_MAX_HEIGHT && (gGameBoard[counter][0] & BORDER_BIT))
	{
		counter ++;
	}

	//ommit the upper and lower borders
	gBoardHeight = counter - 2;

}

/*
 * this function read the board content from the process into our memory
 */
BOOL ExtractBoardContent(HANDLE process)
{
	SIZE_T readSize = 0;
	size_t boardSize = BOARD_MAX_HEIGHT * BOARD_MAX_WIDTH;
	int status = 0;

    status = ReadProcessMemory(process,(LPCVOID)BOARD_MEMORY_ADDRESS,&gGameBoard,boardSize,&readSize);

	if (status == 0)
	{
		_tprintf(_T("Error: can't read process memory\n"));
		return FALSE;
	}

	//check that the entire board been read
	if (readSize != boardSize)
	{
		_tprintf(_T("Error: can't read whole bord from process memory\n"));
		return FALSE;
	}

	return TRUE;
}

/*
 * this function get's board's cell's content and return the char representation
 * of this cell. check question 5 at the README for detailed explanation
 */
char TranslateCellContent(char cellValue)
{
	//contain the 4 lsb of this cell
	char innerVal = cellValue&0x0F;
	
	if (cellValue & MINE_BIT)
	{
		if (!(cellValue & REVEALED_BIT))
		{
			if (innerVal == CLICKED_QUESTION_MARK_VAL || innerVal == QUESTION_MARK_VAL)
			{
				return TRUE_QUESTION_MARK_CH;
			}
			else if (innerVal == FLAG_VAL)
			{
				return FLAGED_CELL_CH;
			}
			else if (innerVal == REVEALED_MINE_VAL)
			{
				return REVEALED_MINE_CH;
			}
			else
			{
				return UNREVEALED_MINE_CELL_CH;
			}
		}
		else
		{
			if (innerVal == EXPLODED_MINE_VAL)
			{
				return EXPLODED_MINE_CH;
			}
			else if (innerVal == REVEALED_MINE_VAL)
			{
				return REVEALED_MINE_CH;
			}
		}
		
	}
	//not a mine
	else
	{
		if (!(cellValue & REVEALED_BIT))
		{
			if (innerVal == CLICKED_QUESTION_MARK_VAL || innerVal == QUESTION_MARK_VAL)
			{
				return QUESTION_MARK_CH;
			}
			else if (innerVal == FLAG_VAL)
			{
				return CROSSED_MINE_CH;
			}
			else if (innerVal == CROSSED_MINE_VAL)
			{
				return CROSSED_MINE_CH;
			}
			else 
			{
				return UNREVEALED_EMPTY_CELL_CH;
			}
		}
		else if (innerVal >= 1 && innerVal <= 9)
		{
			return (REVEALED_CELL_WITH_NUM_CH + innerVal);
		}

	}

	return REVEALED_EMPTY_CELL_CH;
}

/*
 * this function prints the board's content
 */
void PrintBoard()
{
	size_t i,j;

	//iterate over non border cells
	for(i = 1 ; i <= gBoardHeight ; ++i)
	{
		for( j = 1 ; j <= gBoardWidth; ++j)
		{
			//print the representation of this cell
			_tprintf(_T("%c"),TranslateCellContent(gGameBoard[i][j]));
		}
		_tprintf(_T("\n"));
	}
}

/*
 * this function simpulate click on the Minesweeper window at a given board's coordinate
 * the left upper cell is (1,1)
 */
BOOL SimulateClickOnBord(HWND window,int h,int w)
{
	//I got those strange calculation from reverse engneering the translation of mouse point to board's coordinates.
	LRESULT mDown = PostMessage(window,WM_LBUTTONDOWN,MK_LBUTTON,(((h<<0x04) + 0x27)<<0x10) | ((w<<0x04) - 0x04));
	LRESULT mUp = PostMessage(window,WM_LBUTTONUP,0,(((h<<0x04) + 0x27)<<0x10) | ((w<<0x04) - 0x04));
	return (((mDown == MOUSE_POST_MESSAGE_SUCCESS) && (mUp == MOUSE_POST_MESSAGE_SUCCESS))?TRUE:FALSE);
}

/*
 * this function solves the Minesweeper board
 */
BOOL SolveBoard()
{
	size_t i,j;
	HWND window = FindWindow(NULL,WINDOW_NAME);

	if (window == NULL)
	{
		_tprintf(_T("Error: Can't find any window named %s\n"),WINDOW_NAME);
		return FALSE;
	}
	
	//simulate clicks on every non mine cell. start from upper left and continue row by row
	for(i = 1 ; i <= gBoardHeight ; ++i)
	{
		for( j = 1 ; j <= gBoardWidth; ++j)
		{
			
			if ((gGameBoard[i][j] & MINE_BIT) == 0)
			{
				if (!SimulateClickOnBord(window,i,j))
				{
					_tprintf(_T("Error: can't send mouse message to window\n"));
					return FALSE;
				}
				Sleep(SLEEP_PERIOD);
			}
		}
	}

	return TRUE;
}

int _tmain(int argc, _TCHAR* argv[])
{
	DWORD processID;
	HANDLE process;
	
	BOOL status;

	//check arguments
	if (argc != 2)
	{
		_tprintf(_T("Usage: %s [-v|-s]\n"),argv[0]);
		return PROG_RET_ERROR;
	}

	//locate Minesweeper process
	status = GetProcessIDByName(PROCESS_NAME,&processID);

	if (status == FALSE)
	{
		_tprintf(_T("Error: can't locate %s process\n"),PROCESS_NAME);
		return PROG_RET_ERROR;
	}

	//open theis process
	process = OpenProcess(PROCESS_VM_READ, FALSE, processID);

	if (process == NULL)
	{
		_tprintf(_T("Error: can't open process %s\n"),PROCESS_NAME);
		return PROG_RET_ERROR;
	}

	//read the board from the process memory
	if (!ExtractBoardContent(process))
	{
		return PROG_RET_ERROR;
	}

	//extract true board dimensions
	CalculateBoardActualSize();	

	//print the board
	if (_tcsncmp(argv[1], _T(VIEWER_MODE), strlen(VIEWER_MODE)) == 0)
	{
		PrintBoard();
	}
	//solve the board
	else
	{
		if (!SolveBoard())
		{
			return PROG_RET_ERROR;
		}
	}

	return PROG_RET_SUCCESS;
}

